// CoreCalc, a spreadsheet core implementation 

// ----------------------------------------------------------------------
// Copyright (c) 2006-2009 Peter Sestoft and others

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using SCG = System.Collections.Generic;
using System.Reflection.Emit;
using System.Text;
using C5;
using System.Diagnostics;

namespace CoreCalc.SheetDefinedFunctions
{
  /// <summary>
  /// An object such as Cell, Expr, CGExpr that can tell what 
  /// full cell addresses it depends on.
  /// </summary>
  public interface IDepend
  {
    void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn);
  }

  public interface ICompilable : IDepend
  {
    void Compile();
    StrictCellTypeHolder GetStrictCells();
  }

  public class ProgramLines : CodeGenerate
  {
    // sestoft: SCG.LinkedList not SCG.List, probably because it is traversed in weird ways, 
    // and insertions (of ClearVariable objects etc) are performed during traversal
    private readonly SCG.LinkedList<ICompilable> programList;
    // sestoft: This maps a full cell address to the SCG.LinkedList node
    // that computes the value of the formula in that cell:
    private readonly HashDictionary<FullCellAddr, LinkedListNode<ICompilable>> hashProgramList;
    private readonly HashDictionary<LinkedListNode<ICompilable>, FullCellAddr> programListItemToAddress;

    public ProgramLines()
    {
      programList = new System.Collections.Generic.LinkedList<ICompilable>();
      hashProgramList = new HashDictionary<FullCellAddr, LinkedListNode<ICompilable>>();
      programListItemToAddress = new HashDictionary<LinkedListNode<ICompilable>, FullCellAddr>();
    }

    public void AddLineForCell(FullCellAddr ca, ICompilable e)
    {
      LinkedListNode<ICompilable> newNode = programList.AddLast(e);
      hashProgramList.Add(ca, newNode);
      programListItemToAddress.Add(newNode, ca);
    }

    public void Compile()
    {
      if (programList.Count == 0)
        // sestoft: this seems to happen when input and output cells overlap -- why?
        CodeGenerate.ilg.Emit(OpCodes.Ldnull); 
      else
        foreach (ICompilable expr in programList)
          expr.Compile();
    }

    /// <summary>
    /// Insert code to unwrap the computed value of a cell, if
    /// the cell has type Value but is referred to as a Number.  
    /// Also register the unwrapped version of the variable 
    /// in the NumberVariables dictionary.
    /// </summary>
    public void CreateUnwrappedValuesOfStrictCells()
    {
      VariableAndType[] variables = GetStrictCells().AllToArray();
      foreach (VariableAndType holder in variables)
      {
        if (holder.type == Typ.Number && CellReferences[holder.var].Type == Typ.Value)
        {
          Variable numberVar = new LocalVariable(ilg, holder.var + " Number", Typ.Number);
          ComputeUnwrapAndSetVariable setter 
            = new ComputeUnwrapAndSetVariable(new CGCellRef(holder.var), numberVar, holder.var);
          // Insert unwrapper-and-setter right after the code computing the variable's Value
          LinkedListNode<ICompilable> node;
          if (hashProgramList.Find(holder.var, out node))         
            programList.AddAfter(node, setter);
            // Now programList contains: ..., node, setter, ...
          else // Must be one of the input cells, therefore add early
            programList.AddFirst(setter);
          NumberVariables.Add(holder.var, numberVar);
        }
      }
    }

    public StrictCellTypeHolder GetStrictCells()
    {
      StrictCellTypeHolder back = new StrictCellTypeHolder(true);
      foreach (ICompilable expr in programList)
        back.Union(expr.GetStrictCells());
      return back;
    }

    public override string ToString()
    {
      StringBuilder sb = new StringBuilder();
      int counter = 0;
      foreach (ICompilable expr in programList)
        sb.Append(counter++).Append("0: ").AppendLine(expr.ToString());
      return sb.ToString();
    }

    /// <summary>
    /// Creates an "unwrapped" local variable of type double for each
    /// cell that needs one.
    /// Also computes evaluation condition for each cell.
    /// </summary>
    public void End(DependencyGraph dpGraph)
    {
      CreateUnwrappedValuesOfStrictCells();
      ComputeEvalConds(dpGraph);
    }

    public void ComputeEvalConds(DependencyGraph dpGraph)
    {
      // TODO: The threshold could be dynamic and depend on the 
      // size of the evaluation condition: the larger the evaluation 
      // condition, the higher is the threshold for the controlled 
      // expression to be non-trivial.
      const int THRESHOLD = 30;
      // Compute evaluation condition for each cell
      C5.IDictionary<FullCellAddr, PathCond> evalConds =
        new HashDictionary<FullCellAddr, PathCond>();
      evalConds[dpGraph.outputCell] = PathCond.TRUE;
      // Traverse programList backwards to build evaluation conditions
      LinkedListNode<ICompilable> node = programList.Last;
      while (node != null)
      {
        if (node.Value is CGExpr) // This must be the output node
          (node.Value as CGExpr).EvalCond(PathCond.TRUE, evalConds);
        else if (node.Value is ComputeAndSetVariable)
        {
          ComputeAndSetVariable casv = node.Value as ComputeAndSetVariable;
          int bound = THRESHOLD;
          bool isSerious = casv.expr.IsSerious(ref bound);
          PathCond evalCond = evalConds[casv.cellAddr];
          // Console.WriteLine("evalConds[{0}{1}] = {2}\n", casv.cellAddr, isSerious ? "" : ":TRIVIAL", evalCond);
          if (isSerious && !evalCond.Is(true))
          {
            Console.WriteLine("Setting EvalCond[{0}] = {1}", casv.cellAddr, evalCond);
            casv.EvalCond = evalCond.ToCGExpr();
          }
          casv.expr.EvalCond(evalCond, evalConds);
        }
        else
          throw new ImpossibleException("ComputeEvalConds: " + node.Value.GetType());
        node = node.Previous;
      }
    }

    public ICompilable GetProgramListNode(FullCellAddr fca)
    {
      LinkedListNode<ICompilable> programListnode;
      if (hashProgramList.Find(fca, out programListnode))
        return programListnode.Value;
      else
        return null;
    }
  }

  // ----------------------------------------------------------------
  // Auxiliary classes for variable unwrapping and so on

  public class ComputeAndSetVariable : CodeGenerate, ICompilable
  {
    public readonly CGExpr expr;
    public readonly Variable var;
    public readonly FullCellAddr cellAddr;
    private CGExpr evalCond; // If null, then unconditional evaluation

    public ComputeAndSetVariable(CGExpr expr, Variable var, FullCellAddr cellAddr)
    {
      this.expr = expr;
      this.var = var; 
      this.cellAddr = cellAddr;
    }

    public CGExpr EvalCond {
      get { return evalCond; }
      set { this.evalCond = value; }
    }

    public virtual void Compile()
    {
      EvalCondCompile(delegate {
      if (var.Type == Typ.Number)
        expr.CompileToDoubleOrNan();
      else
        expr.Compile();
      var.EmitStore(ilg);
      });
    }

    protected void EvalCondCompile(Act compile)
    {
      if (markLevel == MarkLevel.MarkIV && evalCond != null)
        evalCond.CompileToDoubleProper(
          new Generate(delegate
        {
          Label endLabel = ilg.DefineLabel();
          ilg.Emit(OpCodes.Conv_I4);
          ilg.Emit(OpCodes.Brfalse, endLabel);
          compile();
          ilg.MarkLabel(endLabel);
        }),
          new Generate(delegate { }));
      else
        compile();
    }


    public virtual void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn) {
      expr.DependsOn(here, dependsOn);
      if (evalCond != null)
        evalCond.DependsOn(here, dependsOn);
    }

    public StrictCellTypeHolder GetStrictCells()
    {
      return expr.GetStrictCells();
    }

    public override string ToString()
    {
      return String.Format("{0} = {1}", var.Name, expr);
    }
  }

  /// <summary>
  /// Compute Value, unwrap to double, and set variable of type Number
  /// </summary>
  public class ComputeUnwrapAndSetVariable : ComputeAndSetVariable
  {
    public ComputeUnwrapAndSetVariable(CGExpr expr, Variable var, FullCellAddr cellAddr)
      : base(expr, var, cellAddr) { }

    public override void Compile()
    {
      Debug.Assert(var.Type == Typ.Number);
      EvalCondCompile(delegate
      {
        expr.Compile();
        UnwrapToDoubleOrNan();
        var.EmitStore(ilg);
      });
    }

    public override string ToString()
    {
      return String.Format("{0} = UnwrapToDoubleOrNan({1})", var.Name, expr);
    }
  }
}